在有限的時間中,要學會一個前端框架不容易,也沒有一個框架是萬能的,但總要有個開始,筆者在進幾年一直在 extjs 的世界打滾,已有三四年的經驗,最近負責的專案除了使用 extjs,後端使用 grails,發現好處多多,就開發過程中的經驗與大家分享,從實際的例子還有兩個框架遇到得整合問題一一介紹
筆者之前在寫過一篇關於 extjs 與 grails 的完美組合,大概說明了 extjs 與 grails 整合所帶來的優點,以及兩者 mvc 的結構可以更快速的開發出容易維護的應用程式,更進一步的我們希望兩者的連結對於資料的 mapping 與更新能夠更加直接。
標題所說的更無縫的整合,在這所要說明的正是關於資料的傳遞,在開發 web 應用程式時,很多時候我們需要處理前端資料結構與後端物件的 mapping,以便進行資料的增修改查,假設我們需要建立一個 grid 如下圖:
畫面以及元件的組立在這就不多做說明,會與後端有關係的就是 domain 與 model的定義,兩者各自代表系統在運作時的資料結構,雖然命名不同,性質是一樣的,假設我們有兩個定義在 grails 的 domain 如下:
class Item {
String name
String title=""
static constraints = {
}
}
class Batch {
static belongsTo=[Item:item]
String name
static constraints = {
}
}
很簡單的兩層結構,在 extjs 也要有對應的 model 如下:
Ext.define('MyApp.model.Item', {
extend: 'Ext.data.Model',
fields: [
{
name: 'id'
},
{
name: 'title'
},
{
name: 'name'
}
],
proxy: {
type: 'rest',
url: '/rest/item',
reader: {
type: 'json',
root: 'itemInstanceList',
totalProperty: 'itemInstanceTotal'
}
}
});
Ext.define('MyApp.model.Batch', {
extend: 'Ext.data.Model',
fields: [
{
name: 'id'
},
{
name: 'name'
},
{
mapping: 'item.id',
name: 'item.id'
}
],
proxy: {
type: 'rest',
url: '/rest/batch',
reader: {
type: 'json',
root: 'batchInstanceList',
totalProperty: 'BatchInstanceTotal'
}
}
});
同樣定義了相同的資料結構只不過在 extjs 前端多了 id 是因為在 grails 中 id 欄位將會自動產生,不需要經過定義。
接著要說明兩者之間的資料如何進行交換傳遞,在上述 extjs 的 model 中,proxy 定義了與後端溝通的方式,在這邊我們使用 rest 的方式,reader 定義了回傳回來的資料該如何進行解析,範例的回傳結構如下:
{
"batchInstanceList": [
{
"class": "extjs.grails.sample.Batch",
"id": 1,
"item": {
"class": "Item",
"id": 1
},
"name": "batch1"
},
{
"class": "extjs.grails.sample.Batch",
"id": 2,
"item": {
"class": "Item",
"id": 1
},
"name": "batch2"
}
],
"batchInstanceTotal": 3
}
其中 root 與 totalProperty 所指定的就是在回傳 json 中對應的屬性名稱,更進階的,我們可以多層如同:
{
"itemInstanceList": [
{
"class": "extjs.grails.sample.Batch",
"id": 1,
"item": {
"class": "Item",
"id": 1
},
"name": "batch1"
"route": {
[
{
id:1
name:'route1'
},
{
id:2
name:'route2'
}
]
},
},
{
"class": "extjs.grails.sample.Batch",
"id": 2,
"item": {
"class": "Item",
"id": 1
},
"name": "batch2"
}
],
"batchInstanceTotal": 3
}
這樣的結構下我們可以這樣定義 root:
root: 'batchInstanceList.route',
很多時候 model 會有 hasMany 的關係,json 解析時清楚看到資料的結構,實際使用時也可以根據不同情形取出不同層級的資料內容進行 grid 的呈現。
另一種情形是為了節省 request 的量,會希望一次交易就將多組資料一次從後端取得,可以指定多層的索引方式就格外重要。
說明完了資料結構的定義,我們在進一步來看單筆資料的回傳,假設我們利用 ajax 將資料送出並且回傳格式為 json 內容如下
{
id: 1
item_id: 1
name: "batch2"
}
後端 grails 接收到這樣的資料時,前端所傳入的參數值都存入 params
這個變數以便進行存取取得任何資料,就跟使用 json object 一樣,我們可以這樣進行更新:
// update
def batchInstance=Batch.get(params.id)
batchInstance.properties = params
batchInstance.item=Item.findById(params.item_id)
batchInstance.save()
// create
def batchInstance=new Batch(params)
batchInstance.item=Item.findById(params.item_id)
batchInstance.save()
在使用 grails 時,雖然 update 與 create 的使用方式不同,但我們都可以將整個 json 傳入 domain 讓 grails 自動幫我們進行資料的 mapping,透過 json 裡的每個參數名,而在有多層架構下,初學使用可能會向上述範例程式一樣,需要在查詢一次下層 domain,其實 grails 對於多層的物件也可以自動 mapping 並且查出,假設我們的回資料結構改變為下:
{
id: 1
item.id: 1 // item_id >> item.id
name: 'batch'
}
如此一來在 grails 中我們可以這樣存取 item.id:params.item.id
,也就是說傳入 grails 的參數如果是有多層結構,我們可以在前端 field 之 id 定義成對應的多層結構的如同上述範例,即使是使用 form 的格式也是一樣對應 input field 的 name。
調整完後我們的 update 與 create 可以精簡如下:
// update
def batchInstance=Batch.get(params.id)
batchInstance.properties = params
batchInstance.save()
// create
def batchInstance=new Batch(params)
batchInstance.save()
這樣的特性有什麼好處?在不需要特殊的欄位處理下,對於 domain 欄位的調整或新增我們不需要變動到 update 與 create 的處理,所有中間的資料 mapping 都交由 grails 幫我們處理完成,相信還有更重要的事值得我們去花時間。
關於 grails 的 data binding 方式,讀者可以參考 grails 的官方 7.1.6 Data Binding,裡面有其他更進階方變得 binding 規則,其中在筆者的專案中有遇到一個問題,在要將關連 domain 設為空的時候傳入 null 值一直無法正確運作,在官方說明 binding 的章節中有一段說明:
An association property can be set to null by passing the literal String "null".
像是這種情形,就應該要把要設為 null 的物件其值改為 "null",如此一來我們就可以把關連 domain 設為空,移除該關連。
最後還要在說明的是在使用 architect 定義前端 model 的 name 時不允許輸入 "." 會跳出下圖訊息:
你只要先用 '_' 將他建立完成,之後再進入 field 將 name 中的 _ 改為 '.' 即可,算是兩邊的限制不一樣,基本上 field 中的 name 有 '.' 是不影響運作的。
Ext JS 教學內容由思創軟體提供,共同作者 @lyhcode 與 @smlsun 目前在校園及企業從事 JavaScript(含 Node.js, Ext JS)與 Java(含 Groovy, Grails, Gradle) 教育訓練及顧問工作。